//============================================================================
// Name        : sann.cpp
// Author      : Harlan Underwood
// Version     : 0.0
// Copyright   : Copyright 2011
// Description :
//============================================================================

#include <iostream>
#include <iomanip>
#include <cstdarg>
#include <cstdio>
#include "types.h"
#include "macros.h"
#include "random.h"

#ifndef DEBUG
#define DEBUG 1
#endif

using namespace std;

template <class T> void printHash(const T* t);

//TODO refactor in to pure C++ classes, constructors, and init functions.
static int neuronid = 0;

class neuron {
public:
	unsigned int id;
	float value;
	int n_connections;
	float *weights;
	neuron **children;

	inline void init(float _value, int _n_connections) {
		value = _value;
		n_connections = _n_connections;
		weights = new float[_n_connections];
		children = new neuron*[_n_connections];
	#if DEBUG
		print(cout);
	#endif
	}

	neuron() {
		id = neuronid++;
		value = 0;
		n_connections = 0;
		weights = NULL;
		children = NULL;
	}

	~neuron() {
		cout << "~neuron " << id << " @" << this << "\n";
		delete[] weights;
		delete[] children;
	}

	void print(ostream& out) {
		out << "Neuron@" << this << " : " << value << ", " << n_connections
			<< ", c@" << children << ", w@" <<weights << endl;
	}
};

class layer {
public:
	int n_neurons;
	neuron* neurons;

	inline void init(int _n_neurons) {
		n_neurons = _n_neurons;
		neurons = new neuron[_n_neurons];
	}

	inline void init(int _n_neurons, int n_connections) {
		cout << "Neurons in layer: " << _n_neurons << " Connections per neuron: " << n_connections << "\n";
		n_neurons = _n_neurons;
		neurons = new neuron[n_neurons];
		for(int i = 0; i < n_neurons; i++) {
			neurons[i].init(0.5, n_connections);
		}
	}

	layer() {
		n_neurons = 0;
		neurons = NULL;
	}

	~layer() {
		cout << "~layer @" << this << "\n";
		delete[] neurons;
	}
};

class net {
public:
	int n_layers;
	layer* layers;

	inline void init(int _n_layers) {
		n_layers = _n_layers;
		layers = new layer[_n_layers];
	}

	inline void init(int _n_layers, int* layer_dims) {
		//Feedforward Initialization;
		n_layers = _n_layers;
		layers = new layer[n_layers];
		cout << "N Layers: " << n_layers;
		for(int i = 0; i < n_layers; i++) {
			cout << "\nLayer: " << i << " Dim: " << layer_dims[i] << "\n";
		}

		for(int i = 0; i < n_layers-1; i++) {
			layers[i].init(layer_dims[i],layer_dims[i+1]);
		}
		layers[n_layers-1].init(layer_dims[n_layers-1],0);//Output Layer

	}

	inline void init(int _n_layers, int first_dim, ...) {
		int* layer_dims = new int[_n_layers];

		va_list n_per_layer;
		va_start(n_per_layer, _n_layers-1);
		layer_dims[0] = first_dim;
		for(int i = 1; i < _n_layers; i++) {
			layer_dims[i]=va_arg(n_per_layer, int);
		}
		va_end(n_per_layer);
		init(_n_layers, layer_dims);
		delete[] layer_dims;
	}

	net(int _n_layers) {
		init(_n_layers);
	}

	net(int _n_layers, ...) {
		int* layer_dims = new int[_n_layers];

		va_list n_per_layer;
		va_start(n_per_layer, _n_layers);
		for(int i = 0; i < _n_layers; i++) {
			layer_dims[i]=va_arg(n_per_layer, int);
		}
		va_end(n_per_layer);
		init(_n_layers, layer_dims);
		delete[] layer_dims;
	}

	net() {
		n_layers = 0;
		layers = NULL;
	}

	~net() {
		cout << "~net @" << this << "\n";
		delete[] layers;
	}
};

/*
//void print_neuron(const neuron& n);
//void print_neuron(const neuron* n);

int neuronCount = 0;

neuron* init_neuron(float value, int n_connections) {
	neuron *n = new neuron;
	n->value = value;
	n->n_connections = n_connections;
	n->weights = new float[n_connections];
	n->children = new neuron[n_connections];
#if DEBUG
	print_neuron(n);
#endif
	return n;
}

void init_neuron(neuron *n, float value, int n_connections) {
	n->value = value;
	n->n_connections = n_connections;
	n->weights = new float[n_connections];
	n->children = new neuron[n_connections];
#if DEBUG
	print_neuron(n);
#endif
	//return n;
}

void destroy_neuron(neuron* n) {
	deleteArr(n->weights);
	//n->weights = NULL;
	deleteArr(n->children);
	//n->children = NULL;
#if DEBUG
	print_neuron(n);
#endif
}

layer* init_layer(int n_neurons) {
	layer *l = new layer;
	l->n_neurons = n_neurons;
	l->neurons = new neuron[n_neurons];
	return l;
}

void destroy_layer(layer* l) {
	int i = 0;
	neuron* n = NULL;
	if(l->neurons != NULL) {
		for(i = 0; i < l->n_neurons; i++) {
			n = l->neurons+i;
			if(n != NULL) {
				destroy_neuron(n);
				deleteObj(n);
			}
		}
		deleteArr(l->neurons);
	}
}

net* init_net(int n_layers) {
	net *n = new net;
	n->n_layers = n_layers;
	n->layers = new layer[n_layers];
	return n;
}

void destroy_net(net* n) {
	int i = 0;
	layer* l = NULL;
	if(n->layers != NULL) {
		for(i = 0; i < n->n_layers; i++) {
			l = n->layers+i;
			if(l != NULL) {
				destroy_layer(l);
				deleteObj(l);
			}
		}
		deleteArr(n->layers);
	}
}

void print_neuron(const neuron* n) {
	cout << "Neuron@" << n << " : " << n->value << ", " << n->n_connections
			<< ", c@" << n->children << ", w@" << n->weights << endl;
}

void print_neuron(const neuron& n) {
	print_neuron(&n);
}

void neuron_mem_test() {
	neuron n = *init_neuron(0.5, 6);
	print_neuron(n);
	destroy_neuron(&n);

	neuron m = *init_neuron(0.35, 5);
	cout << "m@" << &m << endl;
	//neuron *c;
	for(int i = 0; i < m.n_connections; i++) {
		init_neuron(&m.children[i],(i+1)*.11,3);
	}
	for(int i = 0; i < m.n_connections; i++) {
		print_neuron(m.children[i]);
	}
	for(int i = 0; i < m.n_connections; i++) {
		cout << "\t child@" << &m.children[i] << endl;
		destroy_neuron(&m.children[i]);
		//delete &m->children[i];
	}
	cout << "m@" << &m << endl;
}
*/

void num_sizes() {
	cout << "char " << sizeof(char) << "\n";
	cout << "short " << sizeof(short) << "\n";
	cout << "short int " << sizeof(short int) << "\n";
	cout << "int " << sizeof(int) << "\n";
	cout << "long int " << sizeof(long int) << "\n";
	cout << "long " << sizeof(long) << "\n";
	cout << "long long " << sizeof(long long) << "\n";
	cout << "float " << sizeof(float) << "\n";
	cout << "double " << sizeof(double) << "\n";
	cout << "long double " << sizeof(long double) << "\n";
	cout << "MTRand " << sizeof(MTRand) << "\n";
	cout << "MWCRand " << sizeof(MWCRand) << "\n";
	cout << "TRand " << sizeof(TRand) << "\n";
	cout << endl;
}

void printIntHash(int n) {
	char *c = new char[4];
	//unsigned int x[4] = {0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000};
	unsigned int j = 0xff000000;

	for(int i = 3; i >=0; i--) {
		//cout << "h" << i << ": " << hex << ((n & j) >> (i*8)) << dec << "\n";
		c[i] = (char)((n & j) >> (i*8));
		//cout << setfill(' ') << hex << (int) c[i] << " ";
		//cout << hex << ((n & j) >> (i*8)) << " " << (0xff & (int) c[i]) << ", ";
		cout << hex << ((n & j) >> (i*8));// << " " << (0xff & (int) c[i]) << ", ";
		j /= 0x100;
	}
	cout << dec << endl;
	delete[] c;
}


template <class T>
void printHash(const T* t) {
	const unsigned int* hash = (const unsigned int*) t;
	cout << "\nt: " << t << " hash: " << hash << " sizeof: " << sizeof(T) <<  "\n";
	cout << hex << setfill('0');
	for(unsigned int i = 0; i < sizeof(T); i++) {
		cout << "0x" << setw(8) << hash[i] << "\n" ;
		//printIntHash(hash[i]);
	}
	cout << dec << setfill(' ');
}

class HashTestObject {
private:
	unsigned int a;
	unsigned int b;
	float c;
public:
	HashTestObject() {
		a = 0x12345678;
		b = 0x87654321;
		c = 0.15625;
	}

	void foo() {
		static const float d = 0.15625;//0x3e200001
		static const float e = 0.15625;
		static const unsigned int f = 0x00000002;
		static const unsigned int g = 0x00000003;
		if(d > e+f+g) {
			foo();
		}
	}
};

int main() {
	MTRand r1(17602);
	MWCRand r2(17602);
	TRand r3(17602);
	//r3.seed(17602);

	cout << "!!!Hello World!!! Wassup?\n\n"; // prints !!!Hello World!!!
	for(int i = 0; i < 20; i++) {
		printf("%12d\t %.7f\t %.7f\n", randint(), randfloat(), randdouble());
		//reseed(5125099);
	}

	num_sizes();
	uint32 a = 0xffffda61;
	float b = 0.35;
	uint32 c = 1051931443;
	cout << sizeof(0xffffda61) << '\n';
	cout << "A: " << hex << a << " as a float " << *((float*) &a) << dec << "\n";
	cout << "B: " << b << " as an int " << hex << *((uint32*) &b) << dec << "\n";
	cout << "C: " << hex << c << " as a float " << pcast(float,c) << dec << "\n";
	uint32 d = 0xefde0001;
	cout << "D: " << hex << d << " as a float " << pcast(float,d) << dec << "\n";
	float e = 1.37411e+029;
	cout << "E: " << e << " as an int " << hex << pcast(int, e) << dec << "\n";
	uint32 f = 0x3e200000;
	cout << "F: " << hex << f << " as a float " << pcast(float,f) << dec << "\n";

	cout << endl;
	int x;
	for(int i = 0; i < 10; i++) {
		x = r1.randInt();
		printf("%12d\t%12.8f\t", x, x*(1.0/4294967295.0));
		x = r2.randInt();
		printf("%12d\t%12.8f\t", x, x*(1.0/4294967295.0));
		x = r3.randInt();
		printf("%12d\t%12.8f\t", x, x*(1.0/4294967295.0));
		cout << endl;
	}

	//printIntHash(0xabcdef12);
	//printIntHash(10);
	cout << "----------\n";
	/*
	const unsigned int* hash = (const unsigned int*) &r3;
	cout << "\nr3: " << &r3 << " hash: " << hash << " sizeof: " << sizeof(TRand) << "\n";
	cout << hex << setfill('0');
	for(unsigned int i = 0; i < sizeof(TRand); i++) {
		cout << "0x" << setw(8) << hash[i] << "\n" ;
		//printIntHash(hash[i]);
	}
	cout << dec << setfill(' ');
	cout <<"----------\n";
	*/
	//printHash(&r3);
	//HashTestObject o;
	//printHash(&o);

	net n1(4,3,4,4,2);
	cout << "\ndone!\n";
	return 0;
}
